precision highp float;
//glsllib
#import "Common/ShaderLib/Lighting.glsllib"
uniform vec4 g_LightData[NB_LIGHTS];
#define PI          3.14159265359f
#define invPI       0.3183098861837697f
#define invTWO_PI   0.15915494309f
#define saturate(x) clamp(x, 0.0f, 1.0f)
const vec2 invAtan = vec2(0.1591f, 0.3183f);

vec3 Diffuse_Lambert( vec3 DiffuseColor )
{
    return DiffuseColor * (1.0f / PI);
}

float D_GGX( float Roughness, float NoH )
{
    float m = Roughness * Roughness;
    float m2 = m * m;
    float d = ( NoH * m2 - NoH ) * NoH + 1.0f;    // 2 mad
    return m2 / ( PI*d*d );                     // 4 mul, 1 rcp
}

float Vis_Smith( float Roughness, float NoV, float NoL )
{
    float a = Roughness * Roughness ;
    float a2 = a*a;

    float Vis_SmithV = NoV + sqrt( NoV * (NoV - NoV * a2) + a2 );
    float Vis_SmithL = NoL + sqrt( NoL * (NoL - NoL * a2) + a2 );
    return 1.0f / ( Vis_SmithV * Vis_SmithL );
}

vec3 F_Schlick( vec3 SpecularColor, float VoH )
{
    float Fc = pow( 1.0f - VoH, 5.0f );
    return Fc + (1.0f - Fc) * SpecularColor;
}

float getAttenuation( vec3 lightPosition, vec3 fragPos, float lightRadius )
{
    float r                = lightRadius;
    vec3 L                 = lightPosition - fragPos;
    float dist             = length(L);
    float d                = max( dist - r, 0.0f );
    L                      /= dist;
    float denom            = d / r + 1.0f;
    float attenuation      = 1.0f / (denom*denom);
    float cutoff           = 0.0052f;
    attenuation            = (attenuation - cutoff) / (1.0f - cutoff);
    attenuation            = max(attenuation, 0.0f);
    
    return attenuation;
}
vec2 getuv(vec3 p){
    float theta = acos(p.y);
    float phi = atan(p.z, p.x);
    if (phi < 0.0f) {
        phi += 2.0f * PI;
    }
    vec2 s;
    s.x = phi * invTWO_PI;
    s.y = theta * invPI;
    return s;
}


vec2 getuv2(vec3 v)
{
    vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
    uv *= invAtan;
    uv.y += 0.5f;
    if(uv.x < 0.0f)
        uv.x += 1.0f;
    return uv;
}

vec3 EnvBRDFApprox( vec3 SpecularColor, float Roughness, float NoV )
{
	const vec4 c0 = vec4( -1.0f, -0.0275f, -0.572f, 0.022f );
	const vec4 c1 = vec4( 1.0f, 0.0425f, 1.04f, -0.04f );
	vec4 r = Roughness * c0 + c1;
	float a004 = min( r.x * r.x, exp2( -9.28f * NoV ) ) * r.x + r.y;
	vec2 AB = vec2( -1.04f, 1.04f ) * a004 + r.zw;
	AB.y *= saturate( 50.0f * SpecularColor.y );
	return SpecularColor * AB.x + AB.y;
}

vec4 lerpMip(vec3 lookup,float mip)
{
	vec2 rauv = getuv(lookup);
	rauv.y = rauv.y * 0.125f;
	float mip1 = floor(mip);
	float mip2 = mip1 + 1.0f;
	mip2 = clamp(mip2, 0.0f, 5.0f);
	vec2 mip1uv = vec2(rauv.x,rauv.y + mip1 * 0.125f);
	vec2 mip2uv = vec2(rauv.x,rauv.y + mip2 * 0.125f);
	return vec4(mip1uv.x,mip1uv.y,mip2uv.x,mip2uv.y);
}

vec3 getHdr(vec4 src)
{
    //vec3 outc = vec3(1.0f,1.0f,1.0f);
    //float e = src.w * 255.0f - 128.0f;
    //e = pow(2.0f,e);
    //outc.x = src.x * e;
    //outc.y = src.y * e;
    //outc.z = src.z * e;
    //return outc;
    return vec3(src);
}

//hdr tone mapping
vec3 toneMap(vec3 src){
    vec3 color = src / (1.0f + src);
    color = pow(color,vec3(1.0f/2.2f,1.0f/2.2f,1.0f/2.2f));
    return color;
}

vec3 getNormalFromMap(sampler2D normalMap, vec2 TexCoords, mat3 TBN)
{
    vec3 tangentNormal = texture2D(normalMap, TexCoords).xyz * 2.0f - 1.0f;

    //vec3 Q1  = dFdx(WorldPos);
    //vec3 Q2  = dFdy(WorldPos);
    //vec2 st1 = dFdx(TexCoords);
    //vec2 st2 = dFdy(TexCoords);

    //vec3 N   = normalize(Normal);
    //vec3 T  = normalize(Q1*st2.t - Q2*st1.t);
    //vec3 B  = -normalize(cross(N, T));
    //mat3 TBN = mat3(T, B, N);

    return normalize(TBN * tangentNormal);
}

vec2 SampleSphericalMap(vec3 v)
{
    vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
    uv *= invAtan;
    uv += 0.5f;
    return uv;
}

vec3 getNormap(vec3 t,vec3 n,vec3 color)
{
    vec3 b = cross(t,n);
    b = normalize(b);
    mat3 tbn = mat3(t,b,n);
    return normalize(tbn * color);
}
const float NORMAL_TYPE = -1.0f;
//jme3 lib
void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
                            vec3 lightColor, float fZero, float roughness, float ndotv,
                            out vec3 outDiffuse, out vec3 outSpecular){
    // Compute halfway vector.
    vec3 halfVec = normalize(lightDir + viewDir);

    // Compute ndotl, ndoth,  vdoth terms which are needed later.
    float ndotl = max( dot(normal,   lightDir), 0.0f);
    float ndoth = max( dot(normal,   halfVec),  0.0f);       
    float hdotv = max( dot(viewDir,  halfVec),  0.0f);

    // Compute diffuse using energy-conserving Lambert.
    // Alternatively, use Oren-Nayar for really rough 
    // materials or if you have lots of processing power ...
    outDiffuse = vec3(ndotl) * lightColor;

    //cook-torrence, microfacet BRDF : http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
   
    float alpha = roughness * roughness;

    //D, GGX normaal Distribution function     
    float alpha2 = alpha * alpha;
    float sum  = ((ndoth * ndoth) * (alpha2 - 1.0f) + 1.0f);
    float denom = PI * sum * sum;
    float D = alpha2 / denom;  

    // Compute Fresnel function via Schlick's approximation.
    float fresnel = fZero + ( 1.0f - fZero ) * pow( 2.0f, (-5.55473f * hdotv - 6.98316f) * hdotv);
    
    //G Shchlick GGX Gometry shadowing term,  k = alpha/2
    float k = alpha * 0.5f;

 /*   
    //classic Schlick ggx
    float G_V = ndotv / (ndotv * (1.0 - k) + k);
    float G_L = ndotl / (ndotl * (1.0 - k) + k);
    float G = ( G_V * G_L );
    
    float specular =(D* fresnel * G) /(4 * ndotv);
   */
 
    // UE4 way to optimise shlick GGX Gometry shadowing term
    //http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html
    float G_V = ndotv + sqrt( (ndotv - ndotv * k) * ndotv + k );
    float G_L = ndotl + sqrt( (ndotl - ndotl * k) * ndotl + k );    
    // the max here is to avoid division by 0 that may cause some small glitches.
    float G = 1.0f/max( G_V * G_L ,0.01f); 

    float specular = D * fresnel * G * ndotl;  
 
    outSpecular = vec3(specular) * lightColor;
}
void main(){
    //vec3 texN 			= texture2D( uNormalMap, iTexCoords).xyz;
    //texN 				= texN * 2.0f - 1.0f;
    //vec3 N 				= getNormap( iTangent.xyz,iNormal,texN);
    vec3 norm = normalize(iNormal);
    vec3 tan = normalize(iTangent.xyz);
    mat3 tbnMat = mat3(tan, iTangent.w * cross( (norm), (tan)), norm);
    vec4 normalHeight = texture2D(uNormalMap, iTexCoords);
    vec3 normal = normalize((normalHeight.xyz * vec3(2.0f, NORMAL_TYPE * 2.0f, 2.0f) - vec3(1.0f, NORMAL_TYPE * 1.0f, 1.0f)));
    normal = normalize(tbnMat * normal);
    vec3 N = normal;
    
    vec4 albeoColor = texture2D(uAlbeoMap, iTexCoords);
    vec3 baseColor  = albeoColor.rgb;
    float roughness = saturate(texture2D(uRoughnessMap, iTexCoords).r + 0.01f);
    //float spec = texture2D(uSpecularMap, iTexCoords).r;
    float ao = texture2D(uAoMap, iTexCoords).r;
    float metallic = texture2D(uMetallicMap, iTexCoords).r;
    //mat3 tbnMat = mat3(iTangent.xyz, iTangent.w * cross( (iNormal), (iTangent.xyz)), iNormal.xyz);
    //vec3 N = getNormalFromMap(uNormalMap, iTexCoords, tbnMat);
    vec3 V               = normalize( uCamPosition - iPosition );
    float NoV            = saturate( dot( N, V ) );
    vec3 diffuseColor    = baseColor - baseColor * metallic;
    vec3 specularColor   = mix( vec3( 0.08f * iSpecular ), baseColor, metallic );
    //float nonMetalSpec = 0.08f * iSpecular;
    //vec3 specularColor = ((nonMetalSpec - nonMetalSpec * metallic) + albeoColor * metallic).rgb;
    

    vec3 dLights = vec3(0.0f);
    for(int i = 0;i < NB_LIGHTS;i+=3){
        vec4 lightColor = g_LightData[i];            
        vec4 lightData1 = g_LightData[i+1];
        
        vec4 lightDir4;
        vec3 lightVec;
        lightComputeDir(iPosition, lightColor.w, lightData1, lightDir4, lightVec);
        
        //vec3 L = normalize(lightData1.xyz - iPosition);
        
        //my code
        //vec3 L               = normalize(lightDir4.xyz);
        //vec3 H               = normalize(V + L);

        //float NoL            = saturate( dot( N, L ) );
        
        //float VoH            = saturate( dot( V, H ) );
        //float NoH            = saturate( dot( N, H ) );

        

        //float attenuation    = getAttenuation( lightData1.xyz, iPosition, 1.0f / lightData1.w );
        //float D              = D_GGX(roughness,NoH);
        //float G              = Vis_Smith(roughness,NoV,NoL); 
        //vec3  F              = F_Schlick(specularColor,VoH);

        //vec3 diffusePoint    = Diffuse_Lambert(diffuseColor);
        //vec3 specularPoint   = D * G * F;
        //dLights             += lightColor.rgb * ( diffusePoint + specularPoint ) * NoL * attenuation;
        //my code
        
        //jme3 lib
        float fallOff = 1.0f;
        //point light attenuation
        fallOff *= lightDir4.w;
        vec3 directDiffuse;
        vec3 directSpecular;
        PBR_ComputeDirectLight(normal, lightDir4.xyz, V,
                            lightColor.rgb,iSpecular, roughness, NoV,
                            directDiffuse,  directSpecular);
        vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular;
        dLights             += directLighting * fallOff;
        //jme3 lib
    }
    
    
    
  
    vec3 color;
    float numMips	 = 5.0f;
    //float mip		 = clamp(log2(roughness + 0.01f) + numMips,0.0f,numMips);
    float mip		 = clamp((roughness + 0.01f) * numMips,0.0f,numMips);
    float mixmip 	 = fract(mip);
    vec3 lookup		 = -reflect( V, N );

    vec4 mipuv 		 = lerpMip(lookup,mip);
    vec3 radiance1	 = getHdr(texture2D( uRadianceMap, mipuv.xy));
    vec3 radiance2	 = getHdr(texture2D( uRadianceMap, mipuv.zw));
    vec3 radiance 	 = mix(radiance1, radiance2,vec3(mixmip,mixmip,mixmip));
    vec3 irradiance	 = getHdr(texture2D( uIrradianceMap,getuv(iNormal) ));

    vec3 reflectance	 = EnvBRDFApprox( specularColor, roughness, NoV );

    vec3 diffuse  	 = diffuseColor * irradiance;
    vec3 specular 	 = radiance * reflectance;
    color		 = diffuse + specular + dLights;
    //color 		 = toneMap(color);

   
    oColor = vec4(color * ao * albeoColor.w, albeoColor.w);

}